package xgen;

import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.io.File;
import java.io.PrintStream;
import java.util.*;

public class Xdb {
    private final Set<String> nameset = new HashSet<>();
    private final List<XBean> xbeans = new ArrayList<>();
    private final List<CBean> cbeans = new ArrayList<>();
    private final Map<String, Table> tables = new HashMap<>();

    private final List<Cache> caches = new ArrayList<>();
    private final Set<String> cacheNames = new HashSet<>();

    private final File xgenOutput;
    private final File xtabledir;
    private final File xbeandir;
    private final File xbeandir__;
    private final File xcachedir;

    Xdb(Element self) {
        xgenOutput = new File(self.getAttribute("xgenOutput"));
        if (!xgenOutput.isDirectory() || !xgenOutput.exists())
            throw new RuntimeException("'" + xgenOutput + "' (!isDirectory || !exists())");

        xtabledir = new File(xgenOutput, "xtable");
        xbeandir = new File(xgenOutput, "xbean");
        xbeandir__ = new File(xbeandir, "__");
        xcachedir = new File(xgenOutput, "xcache");

        NodeList childnodes = self.getChildNodes();
        for (int i = 0; i < childnodes.getLength(); ++i) {
            Node node = childnodes.item(i);
            if (Node.ELEMENT_NODE != node.getNodeType())
                continue;

            Element e = (Element) node;
            String nodename = e.getNodeName();
            if (nodename.equals("xbean")) {
                add(new XBean(e));
            } else if (nodename.equals("table")) {
                add(new Table(e));
            } else if (nodename.endsWith("Conf")) {
                // skip conf
            } else if (nodename.equals("cbean")) {
                add(new CBean(e));
            } else if (nodename.endsWith("define")) {
                Define.getInstance().parse(e);
            } else if (nodename.equals("cache")) {
                add(new Cache(e));
            } else
                throw new RuntimeException("nodename=" + nodename);
        }
    }

    Table getTable(String name) {
        return tables.get(name);
    }

    void add(Cache cc) {
        if (!cacheNames.add(cc.getName()))
            throw new RuntimeException("duplicate ccache name: " + cc.getName());
        caches.add(cc);
    }

    void add(XBean bean) {
        if (!nameset.add(bean.getName()))
            throw new RuntimeException("duplicate bean name " + bean.getName());
        xbeans.add(bean);
    }

    void add(CBean bean) {
        if (!nameset.add(bean.getName()))
            throw new RuntimeException("duplicate bean name " + bean.getName());
        cbeans.add(bean);
    }

    void add(Table table) {
        if (null != tables.put(table.getName(), table))
            throw new RuntimeException("duplicate table name " + table.getName());
    }

    File getXbeandir() {
        return xbeandir;
    }

    File getXtabledir() {
        return xtabledir;
    }

    File getXcachedir() {
        return xcachedir;
    }

    public File getXbeandir__() {
        return xbeandir__;
    }

    void compile() {
        // 编译类型
        for (XBean bean : xbeans)
            bean.compile(this);
        for (CBean bean : cbeans)
            bean.compile(this);
        for (Table table : tables.values())
            table.compile(this);
        for (Cache cache : caches)
            cache.compile(this);

        // 验证关系
        for (XBean bean : xbeans) {
            bean.verify(this);
            bean.depends(); // verify self-depend
        }
        for (CBean bean : cbeans) {
            bean.verify(this);
            bean.depends(); // verify self-depend
        }
        for (Table table : tables.values())
            table.verify(this);

        // report
        for (XBean bean : xbeans) {
            if (bean.getOwnerTables().size() > 1)
                Main.warn("OWNER xbean=" + Main.quote(bean.getName()) + " owners=" + bean.getOwnerTables(), 'o');
        }
        for (CBean bean : cbeans) {
            if (bean.getOwnerTables().size() > 1)
                Main.warn("OWNER cbean=" + Main.quote(bean.getName()) + " owners=" + bean.getOwnerTables(), 'o');
        }
    }

    void make() {
        if (!tables.isEmpty()) {
            xtabledir.mkdirs();
        }

        if (!xbeans.isEmpty()) {
            xbeandir.mkdirs();
            xbeandir__.mkdirs();
        }

        if (!caches.isEmpty()) {
            xcachedir.mkdirs();
        }

        for (XBean bean : xbeans)
            bean.make();

        XBean.make(xbeans);

        for (CBean bean : cbeans) {
            bean.make();
        }
        createSpecialTableSql();
        for (Table table : tables.values()) {
            table.make();
            table.createTableSql();
        }
        for (Cache cache : caches) {
            cache.make();
        }
        Table.make(tables.values());

        Cache.make_ConcurrentCaches_(this, caches);

        Lock.make(tables.values());
        makeMetaData();
    }

    void createSpecialTableSql() {
        String sql = "CREATE TABLE IF NOT EXISTS _meta_(k INT NOT NULL PRIMARY KEY, v MEDIUMBLOB NOT NULL) ENGINE=INNODB DEFAULT CHARSET=UTF8;";
        LoggerFactory.getLogger("tablecreate").info(sql);
        LoggerFactory.getLogger("tableclear").info("DROP TABLE IF EXISTS _meta_;");
        LoggerFactory.getLogger("tableclear").info(sql);
        List<String> tables = Arrays.asList("_sys_");
        for (String table : tables) {
            String createSQL = null;
            String clearSQL = null;
            if (Main.isUseJson()) {
                createSQL = "CREATE TABLE IF NOT EXISTS " + table + "(k VARCHAR(255) NOT NULL PRIMARY KEY, v JSON NOT NULL) ENGINE=INNODB DEFAULT CHARSET=UTF8;";
            } else {
                createSQL = "CREATE TABLE IF NOT EXISTS " + table + "(k VARCHAR(255) NOT NULL PRIMARY KEY, v MEDIUMBLOB NOT NULL) ENGINE=INNODB DEFAULT CHARSET=UTF8;";
            }
            clearSQL = "DROP TABLE IF EXISTS " + table + ";";
            LoggerFactory.getLogger("tablecreate").info(createSQL);
            LoggerFactory.getLogger("tableclear").info(clearSQL);
            LoggerFactory.getLogger("tableclear").info(createSQL);
        }
    }

    void makeMetaData() {
        PrintStream ps = Main.fopen(this.getXtabledir(), "_DatabaseMetaData_");

        ps.println("package xtable;");
        ps.println();
        ps.println();
        ps.println("public class _DatabaseMetaData_ extends xdb.util.DatabaseMetaData {");

        ps.println("	@Override");
        ps.println("	public boolean isVerifyXdb() {");
        ps.println("		return " + (Main.noverify ? "false" : "true") + ";");
        ps.println("	}");

        Collections.sort(xbeans, new Comparator<XBean>() {
            @Override
            public int compare(XBean x1, XBean x2) {
                return x1.getName().compareTo(x2.getName());
            }
        });

        List<Table> lsTables = new ArrayList<>(tables.values());
        Collections.sort(lsTables, new Comparator<Table>() {
            @Override
            public int compare(Table t1, Table t2) {
                return t1.getName().compareTo(t2.getName());
            }
        });

        //Split _DatabaseMetaData_ constructor to small ones
        //To fix: the code of constructor _DatabaseMetaData_() is exceeding the 65535 bytes limit
        int loopCounter = 0;
        int methodCounter = 1;
        ps.println("	public void DatabaseMetaData" + methodCounter + "(){");
        ps.println("		// xbeans");
        for (XBean xbean : xbeans) {
            if (checkPrintNewMethod(loopCounter, methodCounter, ps)) {
                methodCounter++;
                loopCounter = 0;
            }
            ps.println("		{");
            xbean.printMeta(ps, "			");
            ps.println("		}");
            loopCounter++;
        }

        ps.println("		// cbeans");
        for (CBean cbean : cbeans) {
            if (checkPrintNewMethod(loopCounter, methodCounter, ps)) {
                methodCounter++;
                loopCounter = 0;
            }
            ps.println("		{");
            cbean.printMeta(ps, "			");
            ps.println("		}");
            loopCounter++;
        }

        ps.println("		// tables");
        for (Table table : lsTables) {
            if (checkPrintNewMethod(loopCounter, methodCounter, ps)) {
                methodCounter++;
                loopCounter = 0;
            }
            table.printMeta(ps, "		");
            loopCounter++;
        }
        ps.println("	}");

        // construct meta data
        ps.println("	public _DatabaseMetaData_() {");
        for (int i = 0; i < methodCounter; i++) {
            ps.println("		DatabaseMetaData" + (i + 1) + "();");
        }
        //added end
        ps.println("	}");
        ps.println("}");
        ps.println();
        ps.close();
    }

    private boolean checkPrintNewMethod(int loopCounter, int methodCounter, PrintStream ps) {
        if (loopCounter < 100)
            return false;

        ps.println("	}");
        ps.println("	public void DatabaseMetaData" + (methodCounter + 1) + "(){");
        return true;
    }

    void clean() {
        CachedFileOutputStream.removeOtherFiles(xbeandir);
        CachedFileOutputStream.removeOtherFiles(xtabledir);
    }
}
